package fhq.hcmute.edu.vn.camera;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PreviewCallback;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.Toast;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;

import fhq.hcmute.edu.vn.activities.ConnectionHandler;
import fhq.hcmute.edu.vn.activities.R;
import fhq.hcmute.edu.vn.util.BundleUtil;
import fhq.hcmute.edu.vn.util.ProjectUtil;

public class CameraControls {

	private static final long AUTO_FOCUS_DELAY = 2000L;
	private Camera mCamera;
	private int cameraId = 0;

	private static final String TAG = "CameraConfiguration";

	private static final int MIN_PREVIEW_PIXELS = 470 * 320; // normal screen
	private static final int MAX_PREVIEW_PIXELS = 1280 * 720;
	private Activity mActivity;
	private MultiFormatReader multiFormatReader;
	private boolean previewing = true;
	private Handler autoFocusHandler;
	private CameraPreview mPreview;
	private static final int MIN_FRAME_WIDTH = 240;
	private static final int MIN_FRAME_HEIGHT = 240;
	private static final int MAX_FRAME_WIDTH = 640;
	private static final int MAX_FRAME_HEIGHT = 480;
	private Rect framingRect;
	private boolean initialized;
	private LinearLayout boder;

	public final CameraPreview showCameraPreview(final Activity activity) {
		mPreview =
				new CameraPreview(activity, getmCamera(), previewCb,
						autoFocusCallBack);
		return mPreview;
	}

	public CameraControls(final Activity activity) {
		this.mActivity = activity;
		autoFocusHandler = new Handler();
		multiFormatReader = new MultiFormatReader();

	}

	public final boolean releaseCamera() {
		if (mCamera != null) {
			previewing = false;
			mCamera.setPreviewCallback(null);
			mCamera.release();
			mCamera = null;
			return true;
		}
		return false;
	}

	public final void open() throws Exception {
		if (!mActivity.getPackageManager().hasSystemFeature(
				PackageManager.FEATURE_CAMERA)) {
			throw new Exception("No Camera Found");
		} else {
			cameraId = findBackFacingCamera();
			this.mCamera = Camera.open(cameraId);

			if (!initialized) {
				initialized = true;
				boder = (LinearLayout) mActivity.findViewById(R.id.mBo);
			}
		}
	}

	private Runnable autoFocus = new Runnable() {
		public void run() {
			if (previewing) {
				mCamera.autoFocus(autoFocusCallBack);
			}
		}
	};

	private int findBackFacingCamera() {
		int cameraId = -1;
		// Search for the back facing camera
		int numberOfCameras = Camera.getNumberOfCameras();
		for (int i = 0; i < numberOfCameras; i++) {
			CameraInfo info = new CameraInfo();
			Camera.getCameraInfo(i, info);
			if (info.facing == CameraInfo.CAMERA_FACING_BACK) {
				cameraId = i;
			}
		}
		return cameraId;
	}

	private AutoFocusCallback autoFocusCallBack = new AutoFocusCallback() {
		public void onAutoFocus(final boolean success, final Camera camera) {
			autoFocusHandler.postDelayed(autoFocus, AUTO_FOCUS_DELAY);
		}
	};

	public synchronized Rect getFramingRect() {
		if (framingRect == null) {
			if (mCamera == null) {
				return null;
			}
			Point screenResolution = getScreenResolution();
			if (screenResolution == null) {
				// Called early, before init even finished
				return null;
			}
			int width = screenResolution.x * 3 / 4;
			if (width < MIN_FRAME_WIDTH) {
				width = MIN_FRAME_WIDTH;
			} else if (width > MAX_FRAME_WIDTH) {
				width = MAX_FRAME_WIDTH;
			}
			int height = screenResolution.y * 3 / 4;
			if (height < MIN_FRAME_HEIGHT) {
				height = MIN_FRAME_HEIGHT;
			} else if (height > MAX_FRAME_HEIGHT) {
				height = MAX_FRAME_HEIGHT;
			}
			int leftOffset = (screenResolution.x - width) / 2;
			int topOffset = (screenResolution.y - height) / 2;
			framingRect = new Rect(leftOffset, topOffset, leftOffset + width,
					topOffset + height);
			Log.d(TAG, "Calculated framing rect: " + framingRect);
		}
		return framingRect;
	}

	private Point findBestPreviewSizeValue(Camera.Parameters parameters,
			Point screenResolution) {

		List<Camera.Size> rawSupportedSizes = parameters
				.getSupportedPreviewSizes();
		if (rawSupportedSizes == null) {
			Log.w(TAG,
					"Device returned no supported preview sizes; using default");
			Camera.Size defaultSize = parameters.getPreviewSize();
			return new Point(defaultSize.width, defaultSize.height);
		}

		// Sort by size, descending
		List<Camera.Size> supportedPreviewSizes = new ArrayList<Camera.Size>(
				rawSupportedSizes);
		Collections.sort(supportedPreviewSizes, new Comparator<Camera.Size>() {
			@Override
			public int compare(Camera.Size a, Camera.Size b) {
				int aPixels = a.height * a.width;
				int bPixels = b.height * b.width;
				if (bPixels < aPixels) {
					return -1;
				}
				if (bPixels > aPixels) {
					return 1;
				}
				return 0;
			}
		});

		if (Log.isLoggable(TAG, Log.INFO)) {
			StringBuilder previewSizesString = new StringBuilder();
			for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
				previewSizesString.append(supportedPreviewSize.width)
						.append('x').append(supportedPreviewSize.height)
						.append(' ');
			}
			Log.i(TAG, "Supported preview sizes: " + previewSizesString);
		}

		Point bestSize = null;
		float screenAspectRatio = (float) screenResolution.x
				/ (float) screenResolution.y;

		float diff = Float.POSITIVE_INFINITY;
		for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
			int realWidth = supportedPreviewSize.width;
			int realHeight = supportedPreviewSize.height;
			int pixels = realWidth * realHeight;
			if (pixels < MIN_PREVIEW_PIXELS || pixels > MAX_PREVIEW_PIXELS) {
				continue;
			}
			boolean isCandidatePortrait = realWidth < realHeight;
			int maybeFlippedWidth = isCandidatePortrait ? realHeight
					: realWidth;
			int maybeFlippedHeight = isCandidatePortrait ? realWidth
					: realHeight;
			if (maybeFlippedWidth == screenResolution.x
					&& maybeFlippedHeight == screenResolution.y) {
				Point exactPoint = new Point(realWidth, realHeight);
				Log.i(TAG, "Found preview size exactly matching screen size: "
						+ exactPoint);
				return exactPoint;
			}
			float aspectRatio = (float) maybeFlippedWidth
					/ (float) maybeFlippedHeight;
			float newDiff = Math.abs(aspectRatio - screenAspectRatio);
			if (newDiff < diff) {
				bestSize = new Point(realWidth, realHeight);
				diff = newDiff;
			}
		}
		if (bestSize == null) {
			Camera.Size defaultSize = parameters.getPreviewSize();
			bestSize = new Point(defaultSize.width, defaultSize.height);
			Log.i(TAG, "No suitable preview sizes, using default: " + bestSize);
		}

		Log.i(TAG, "Found best approximate preview size: " + bestSize);
		return bestSize;
	}

	@SuppressWarnings("deprecation")
	private Point getScreenResolution() {
		WindowManager manager = (WindowManager) mActivity
				.getSystemService(Context.WINDOW_SERVICE);
		Display display = manager.getDefaultDisplay();
		Point point = new Point();
		if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) {
			display.getSize(point);
		} else {
			point = new Point(display.getWidth(), display.getHeight());
		}

		int width = point.x;
		int height = point.y;
		if (width < height) {
			int temp = width;
			width = height;
			height = temp;
		}
		return point;
	}

	private void createFramingRect(int left, int top, int right, int bottom) {
		FrameLayout.LayoutParams layoutParams =
				new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
						LayoutParams.MATCH_PARENT);
		layoutParams.setMargins(left, top, right, bottom);
		boder.setLayoutParams(layoutParams);
	}

	private Rect createRectFraming() {
		Point point =
				findBestPreviewSizeValue(mCamera.getParameters(),
						getScreenResolution());
		Point nPoint = resize(point.x, point.y, 1.5f);
		int left = (point.x - nPoint.x) / 2;
		int width = nPoint.x;
		int height = nPoint.y;
		if (width > MAX_FRAME_WIDTH) {
			width = MAX_FRAME_WIDTH;
		}
		if (height > MAX_FRAME_HEIGHT) {
			height = MAX_FRAME_HEIGHT;
		}
		int top = (point.y - nPoint.y) / 2;

		return new Rect(left, top, width, height);
	}

	private Point resize(float width, float height, float scale) {
		float ratio = width / height;
		float s = width * height;
		float k = scale * ratio;
		int newHeight = (int) Math.sqrt(s / k);
		int newWidth = (int) (newHeight * ratio);
		return new Point(newWidth, newHeight);
	}

	private PreviewCallback previewCb = new PreviewCallback() {
		public void onPreviewFrame(final byte[] data, final Camera camera) {
			Rect rect = createRectFraming();
			createFramingRect(rect.left, rect.top, rect.left,
					140);
			Camera.Parameters parameters = camera.getParameters();
			Log.v(fhq.hcmute.edu.vn.util.TAG.NAME,
					String.format("%d,%d,%d,%d,%d,%d",
							parameters.getPreviewSize().width,
							parameters.getPreviewSize().height,
							rect.left, rect.top, rect.width(),
							rect.height()));
			if (rect != null) {
				Result rawResult = null;
				PlanarYUVLuminanceSource source =
						new PlanarYUVLuminanceSource(data,
								parameters.getPreviewSize().width,
								parameters.getPreviewSize().height,
								rect.left, rect.top, rect.width(),
								rect.height(),
								false);
				if (source != null) {
					BinaryBitmap bitmap =
							new BinaryBitmap(new HybridBinarizer(source));
					try {
						rawResult = multiFormatReader.decode(bitmap);
					} catch (ReaderException re) {
					} finally {
						multiFormatReader.reset();
					}
					if (rawResult != null) {
						if (rawResult.getBarcodeFormat().equals(
								BarcodeFormat.QR_CODE)) {
							previewing = false;
							mCamera.setPreviewCallback(null);
							mCamera.stopPreview();
							Message message = new Message();
							message.what = ProjectUtil.BARCODE_SCAN_RESULT;
							Bundle bundle = new Bundle();
							bundle.putString(BundleUtil.BARCODE_DATA_KEY,
									rawResult.getText());
							message.setData(bundle);
							ConnectionHandler.getInstance(mActivity).
									sendMessage(message);
						} else {
							Toast.makeText(mActivity,
									mActivity.getResources().getString(
											R.string.Toast_invaild_Code),
									Toast.LENGTH_SHORT).show();
						}
					}
				}
			}
		}
	};

	public final Camera getmCamera() {
		return mCamera;
	}

	public final void setmCamera(final Camera mCamera) {
		this.mCamera = mCamera;
	}
}
